/**
  ******************************************************************************
  * @file    bsp_nand.c
  * @author  Iron
  * @date    2018-07-27
  * @version v1.0
  * @brief   bsp nand c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include <string.h>
#include "stm32f4xx_hal.h"
#include "bsp_nand.h"

/* BEBUG LOG */
#include "debug_log.h"
#define LOG_LOCAL_LEVEL DBG_LOG_DEBUG // DBG_LOG_INFO  DBG_LOG_DEBUG
DBG_LOG_TAG("NAND");

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
/* private variables ---------------------------------------------------------*/
static uint32_t FSMC_Initialized = 0;
static uint32_t FSMC_DeInitialized = 0;
NAND_HandleTypeDef hnand1;

#ifdef NAND_RTOS_ENABLE
    SemaphoreHandle_t nand_mutex_handle;
    StaticSemaphore_t nand_mutex;
#endif

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/

static void HAL_FSMC_MspInit(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (FSMC_Initialized)
    {
        return;
    }
    FSMC_Initialized = 1;

    /* Peripheral clock enable */
    __HAL_RCC_FSMC_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();

    /** FSMC GPIO Configuration
    PD14   ------> FSMC_D0
    PD15   ------> FSMC_D1
    PD0    ------> FSMC_D2
    PD1    ------> FSMC_D3
    PE7    ------> FSMC_D4
    PE8    ------> FSMC_D5
    PE9    ------> FSMC_D6
    PE10   ------> FSMC_D7
    PD11   ------> FSMC_CLE  -- xx ALE
    PD12   ------> FSMC_ALE  -- xx CLE
    PD4    ------> FSMC_NOE
    PD5    ------> FSMC_NWE
    PD6    ------> FSMC_NWAIT
    PD7    ------> FSMC_NCE2
    */
    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_14 | GPIO_PIN_15
                          | GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5
                          | GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FSMC;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}

void HAL_NAND_MspInit(NAND_HandleTypeDef *hnand)
{
    HAL_FSMC_MspInit();
}

static void HAL_FSMC_MspDeInit(void)
{
    if (FSMC_DeInitialized)
    {
        return;
    }
    FSMC_DeInitialized = 1;

    /* Peripheral clock enable */
    __HAL_RCC_FSMC_CLK_DISABLE();

    /** FSMC GPIO Configuration
    PD14   ------> FSMC_D0
    PD15   ------> FSMC_D1
    PD0    ------> FSMC_D2
    PD1    ------> FSMC_D3
    PE7    ------> FSMC_D4
    PE8    ------> FSMC_D5
    PE9    ------> FSMC_D6
    PE10   ------> FSMC_D7
    PD11   ------> FSMC_CLE  -- xx ALE
    PD12   ------> FSMC_ALE  -- xx CLE
    PD4    ------> FSMC_NOE
    PD5    ------> FSMC_NWE
    PD6    ------> FSMC_NWAIT
    PD7    ------> FSMC_NCE2
    */
    HAL_GPIO_DeInit(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10);

    HAL_GPIO_DeInit(GPIOD, GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_14 | GPIO_PIN_15
                    | GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5
                    | GPIO_PIN_6 | GPIO_PIN_7);
}

void HAL_NAND_MspDeInit(NAND_HandleTypeDef *hnand)
{
    HAL_FSMC_MspDeInit();
}


/*
 * FSMC initialization function
 *  MEMxSET = tCLS - tWP >= 0ns
        MEMxWAIT = tWP  >= 12ns
        MEMxHOLED = tCLH >= 5ns
        MEMxHIZ = tCLS - tDS >= 0ns
 */
static void MX_FSMC_Init(void)
{
    FSMC_NAND_PCC_TimingTypeDef ComSpaceTiming;
    FSMC_NAND_PCC_TimingTypeDef AttSpaceTiming;

    /** Perform the NAND1 memory initialization sequence
    */
    hnand1.Instance = FSMC_NAND_DEVICE;
    /* hnand1.Init */
    hnand1.Init.NandBank = FSMC_NAND_BANK2;
    hnand1.Init.Waitfeature = FSMC_NAND_PCC_WAIT_FEATURE_ENABLE;
    hnand1.Init.MemoryDataWidth = FSMC_NAND_PCC_MEM_BUS_WIDTH_8;
    hnand1.Init.EccComputation = FSMC_NAND_ECC_DISABLE;
    hnand1.Init.ECCPageSize = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE;
    hnand1.Init.TCLRSetupTime = 1; /* 10ns */
    hnand1.Init.TARSetupTime = 1;  /* 10ns */
    /* hnand1.Config */
    hnand1.Config.PageSize = BSP_NAND_PAGE_DATA_SIZE;  // 1024
    hnand1.Config.SpareAreaSize = BSP_NAND_PAGE_SPARE_SIZE; // 64
    hnand1.Config.BlockSize = BSP_NAND_PAGES_PER_BLOCK;  // 64
    hnand1.Config.BlockNbr = 1024;
    hnand1.Config.PlaneNbr = 1024;
    hnand1.Config.PlaneSize = 64;
    hnand1.Config.ExtraCommandEnable = DISABLE;
    /* ComSpaceTiming */
    ComSpaceTiming.SetupTime = 1;
    ComSpaceTiming.WaitSetupTime = 3;
    ComSpaceTiming.HoldSetupTime = 1;
    ComSpaceTiming.HiZSetupTime = 3;
    /* AttSpaceTiming */
    AttSpaceTiming.SetupTime = 1;
    AttSpaceTiming.WaitSetupTime = 3;
    AttSpaceTiming.HoldSetupTime = 1;
    AttSpaceTiming.HiZSetupTime = 3;

    if (HAL_NAND_Init(&hnand1, &ComSpaceTiming, &AttSpaceTiming) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}

/* DMA ----------------------------------------------------------------------*/
#ifdef NAND_DMA_ENABLE
SemaphoreHandle_t nand_dma_sem_handle;
StaticSemaphore_t nand_dma_sem;
static DMA_HandleTypeDef hdma_memtomem_dma2_stream0;

void DMA2_Stream0_IRQHandler(void)
{
    BSP_NAND_ENTER_ISR();
    HAL_DMA_IRQHandler(&hdma_memtomem_dma2_stream0);
    BSP_NAND_EXIT_ISR();
}

static void nand_dma_transfer_complete(DMA_HandleTypeDef *DmaHandle)
{
    HAL_NVIC_DisableIRQ(DMA2_Stream0_IRQn);
    BSP_NAND_DMA_ISR_RTOS_SEM_RELEASE();
}

static void nand_dma_transfer_error(DMA_HandleTypeDef *DmaHandle)
{
    HAL_NVIC_DisableIRQ(DMA2_Stream0_IRQn);
    BSP_NAND_DMA_ISR_RTOS_SEM_RELEASE();
}

int32_t bsp_nand_dma_write(const uint8_t *dst, size_t size)
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA2_CLK_ENABLE();

    /* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
    hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
    hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;
    hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
    hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
    hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_DISABLE;
    hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
    hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
    hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;

    hdma_memtomem_dma2_stream0.XferCpltCallback  = nand_dma_transfer_complete;
    hdma_memtomem_dma2_stream0.XferErrorCallback = nand_dma_transfer_error;

    if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    BSP_NAND_DMA_RTOS_SEM_CLEAR();

    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, BSP_NAND_DMA_PRE_PRI, BSP_NAND_DMA_SUB_PRI);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
    if (HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t)dst, NAND_ADDRESS, size) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    BSP_NAND_DMA_RTOS_SEM_WAIT();

    return BSP_NAND_OK;
}

int32_t bsp_nand_dma_read(uint8_t *dst, size_t size)
{
    /* DMA controller clock enable */
    __HAL_RCC_DMA2_CLK_ENABLE();

    /* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
    hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
    hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;
    hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
    hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
    hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
    hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
    hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;

    hdma_memtomem_dma2_stream0.XferCpltCallback  = nand_dma_transfer_complete;
    hdma_memtomem_dma2_stream0.XferErrorCallback = nand_dma_transfer_error;

    if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    BSP_NAND_DMA_RTOS_SEM_CLEAR();

    HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, BSP_NAND_DMA_PRE_PRI, BSP_NAND_DMA_SUB_PRI);
    HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
    if (HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, NAND_ADDRESS, (uint32_t)dst, size) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    BSP_NAND_DMA_RTOS_SEM_WAIT();

    return BSP_NAND_OK;
}
#endif // NAND_DMA_ENABLE

/*---------------------------------------------------------------------------*/
int32_t bsp_nand_data_correct(uint32_t generatedEcc, uint32_t readEcc, uint8_t *data)
{
#define ECC_MASK28    0x0FFFFFFF          /* 28 valid ECC parity bits. */
#define ECC_MASK      0x05555555          /* 14 ECC parity bits.       */

    uint32_t count, bitNum, byteAddr;
    uint32_t mask;
    uint32_t syndrome;
    uint32_t eccP;                            /* 14 even ECC parity bits. */
    uint32_t eccPn;                           /* 14 odd ECC parity bits.  */

    syndrome = (generatedEcc ^ readEcc) & ECC_MASK28;
    if (syndrome == 0)
    {
        return (BSP_NAND_OK);                  /* No errors in data. */
    }

    eccPn = syndrome & ECC_MASK;              /* Get 14 odd parity bits.  */
    eccP  = (syndrome >> 1) & ECC_MASK;       /* Get 14 even parity bits. */

    if ((eccPn ^ eccP) == ECC_MASK)           /* 1-bit correctable error ? */
    {
        bitNum = (eccP & 0x01) | ((eccP >> 1) & 0x02) | ((eccP >> 2) & 0x04);
        DBG_LOGD(TAG, "ECC bit %d\n", bitNum);
        byteAddr = ((eccP >>  6) & 0x001) |
                   ((eccP >>  7) & 0x002) |
                   ((eccP >>  8) & 0x004) |
                   ((eccP >>  9) & 0x008) |
                   ((eccP >> 10) & 0x010) |
                   ((eccP >> 11) & 0x020) |
                   ((eccP >> 12) & 0x040) |
                   ((eccP >> 13) & 0x080) |
                   ((eccP >> 14) & 0x100) |
                   ((eccP >> 15) & 0x200) |
                   ((eccP >> 16) & 0x400) ;

        data[ byteAddr ] ^= 1 << bitNum;

        return BSP_NAND_ECC_OK;
    }

    /* Count number of one's in the syndrome. */
    count = 0;
    mask  = 0x00800000;
    while (mask)
    {
        if (syndrome & mask)    count++;
        mask >>= 1;
    }

    if (count == 1)   /* Error in the ECC itself. */
    {
        return BSP_NAND_ECC_ERROR;
    }

    return BSP_NAND_FAIL; /* Unable to correct data. */
#undef ECC_MASK
#undef ECC_MASK24
}

int32_t bsp_nand_wait_rb(uint32_t st, uint32_t timeout_us)
{
    while (timeout_us--)
    {
        nand_delay_us(1);
        if (nand_get_rb() == st)
        {
            break;
        }
    }
    return timeout_us;
}

uint32_t bsp_nand_read_status(void)
{
    uint32_t status = 0;

    /* Send Read status operation command */
    NAND_CMD(NAND_CMD_STATUS);
    /* Read status register data      */
    status = NAND_DATA_READ_BYTE();

    return status;
}

int32_t bsp_nand_reset(void)
{
    uint32_t i, status;

    for (i = 0; i < 10; i++)
    {
        NAND_CMD(NAND_CMD_RESET);

        /* TWB 100us */
        bsp_nand_wait_rb(NAND_RB_RESET, 10);
        /* TPROG 400us / max 900us*/
        bsp_nand_wait_rb(NAND_RB_SET, 500);

        status = bsp_nand_read_status();
        if (status & NAND_READY)
        {
            break;
        }

        DBG_LOGW(TAG, "Nand Flash Wait Ready... (status=%X, times=%d)", status, i);
    }

    return (i >= 10) ? BSP_NAND_FAIL : BSP_NAND_OK;
}

int32_t bsp_nand_flash_check_id(void)
{
    uint8_t id[5];

    NAND_CMD(NAND_CMD_READID);
    NAND_ADDR(0x00);
    id[0] = NAND_DATA_READ_BYTE();
    id[1] = NAND_DATA_READ_BYTE();
    id[2] = NAND_DATA_READ_BYTE();
    id[3] = NAND_DATA_READ_BYTE();
    id[4] = NAND_DATA_READ_BYTE();

    if (id[0] == 0xEC && id[1] == 0xF1)   // K9F1G08U0E: EC F1 00 95 41
    {
        DBG_LOGI(TAG, "Nand Flash K9F1G08U0E Found.(Maker Code:%02X, Device Code:%02X)", id[0], id[1]);
        return BSP_NAND_OK;
    }

    DBG_LOGE(TAG, "Unknow Nand Flash.(Maker Code:%02X Device Code:%02d)", id[0], id[1]);
    return BSP_NAND_FAIL;
}

//int32_t bsp_nand_flash_move_page(const bsp_nand_handle_t *hnand, uint32_t src_page, uint32_t dst_page)
//{
//  int32_t res = BSP_NAND_OK;

//  BSP_NAND_RTOS_MUTEX_WAIT();

//  NAND_CMD(0x00);
//  NAND_ADDR(0x00);                                    // Column Address
//  NAND_ADDR(0x00 >> 8);                           // Column Address
//  NAND_ADDR(src_page & 0xFF);         // Row Address
//  NAND_ADDR((src_page >> 8) & 0xFF);  // Row Address
//  NAND_CMD(0x35);

//  /* TWB 100us */
//  bsp_nand_wait_rb(NAND_RB_RESET, 100);
//  /* TR 40us */
//  bsp_nand_wait_rb(NAND_RB_SET, 40);

//  NAND_CMD(0x85);
//  NAND_ADDR(0x00);                                    // Column Address
//  NAND_ADDR(0x00 >> 8);                           // Column Address
//  NAND_ADDR(src_page & 0xFF);         // Row Address
//  NAND_ADDR((src_page >> 8) & 0xFF);  // Row Address
//  NAND_CMD(0x10);

//  /* TWB 100us */
//  bsp_nand_wait_rb(NAND_RB_RESET, 100);
//  /* TPROG 400us / max 900us*/
//  bsp_nand_wait_rb(NAND_RB_SET, 900);

//  if (bsp_nand_read_status() & NAND_ERROR)
//  {
//    bsp_nand_reset();
//    res = BSP_NAND_FAIL;
//    DBG_LOGE(TAG, "Nand flash move page program error. (src_page %d, dst_page %d)", src_page, dst_page);
//  }
//  else
//  {
//    DBG_LOGD(TAG, "Nand flash move src_page %d to dst_page %d.", src_page, dst_page);
//  }

//  BSP_NAND_RTOS_MUTEX_RELEASE();
//  return res;
//}

//int32_t bsp_nand_flash_erase_block(const bsp_nand_handle_t *hnand, uint32_t block)
//{
//  int32_t res = BSP_NAND_OK;
//  uint32_t block_page;

//  block_page = (block + hnand->block_start) << 6; // block * device->pages_per_block (64)

//  BSP_NAND_RTOS_MUTEX_WAIT();

//  NAND_CMD(NAND_CMD_ERASE0); // 0x60
//  NAND_ADDR(block_page & 0xFF);         // Row Address
//  NAND_ADDR((block_page >> 8) & 0xFF);  // Row Address
//  NAND_CMD(NAND_CMD_ERASE1); // 0xD0

//  /* TWB 100us */
//  bsp_nand_wait_rb(NAND_RB_RESET, 100);
//  /* tBERS 4.5ms / max 16ms*/
//  bsp_nand_wait_rb(NAND_RB_SET, 16000);

//  if (bsp_nand_read_status() & NAND_ERROR)
//  {
//    bsp_nand_reset();
//    res = BSP_NAND_FAIL;
//    DBG_LOGE(TAG, "Nand flash erase block %d (page:%d) error.", block, block_page);
//  }
//  else
//  {
//    DBG_LOGD(TAG, "Nand flash erase block %d (page:%d).", block, block_page);
//  }

//  BSP_NAND_RTOS_MUTEX_RELEASE();
//  return res;
//}

//int32_t bsp_nand_flash_check_block(const bsp_nand_handle_t *hnand, uint32_t block)
//{
//// DBG_LOGI(TAG, "Nand Flash check block %d", block);
//  return BSP_NAND_OK;
//}

//int32_t bsp_nand_flash_mark_badblock(const bsp_nand_handle_t *hnand, uint32_t block)
//{
//  DBG_LOGE(TAG, "Nand Flash mark bad block %d", block);
//  return BSP_NAND_OK;
//}

int32_t bsp_nand_flash_init(void)
{
    int32_t res;

    MX_FSMC_Init();

    BSP_NAND_RTOS_MUTEX_INIT();
    BSP_NAND_DMA_RTOS_SEM_INIT();

    // check nandflash id
    BSP_NAND_RTOS_MUTEX_WAIT();

    res = bsp_nand_reset();
    if (res != BSP_NAND_OK)
    {
        DBG_LOGE(TAG, "bsp nand flash reset wait ready error.");
    }
    else
    {
        res = bsp_nand_flash_check_id();
        if (res != BSP_NAND_OK)
        {
            DBG_LOGE(TAG, "bsp nand reset check chip id error.");
        }
    }

    BSP_NAND_RTOS_MUTEX_RELEASE();

    if (res != BSP_NAND_OK)
    {
        DBG_LOGE(TAG, "nand flash Init Error.");
        return BSP_NAND_FAIL;
    }

    return BSP_NAND_OK;
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
